package net.atomarrow.controllers;

import com.alibaba.fastjson.TypeReference;
import net.atomarrow.action.AtomarrowRequest;
import net.atomarrow.annotation.Inject;
import net.atomarrow.annotation.UseSetMethod;
import net.atomarrow.bean.UploadFile;
import net.atomarrow.controllers.converter.TypeConverter;
import net.atomarrow.controllers.converter.TypeMismatchException;
import net.atomarrow.domains.Domain;
import net.atomarrow.ioc.ActionContext;
import net.atomarrow.upload.MultipartRequest;
import net.atomarrow.util.DateUtil;
import net.atomarrow.util.EnumUtil;
import net.atomarrow.util.NumberUtil;
import net.atomarrow.util.StringUtil;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ParamParser {
	@Inject
	private TypeConverter typeConverter;

	public Date parseRequestDate(String name, String pattern, HttpServletRequest request) {
		return parseDateByPattern(parseRequestString(name, request), pattern);
	}

	public Date parseRequestDate(String name, HttpServletRequest request) {
		return parseDateByPattern(parseRequestString(name, request), null);
	}
	public Date parseRequestTime(String name, HttpServletRequest request) {
		return parseTime(parseRequestString(name, request));
	}

	private Date parseDateByPattern(String value, String pattern) {
		if (StringUtil.isBlank(value)) {
			return null;
		}
		return DateUtil.getDate(value, pattern);
	}

	private Date parseTime(String value) {
		if (StringUtil.isBlank(value)) {
			return null;
		}
		return DateUtil.getTime(value);
	}

	public Integer parseRequestInteger(String name, HttpServletRequest request) {
		return parseRequestInteger(parseRequestString(name, request));
	}

	private Integer parseRequestInteger(String value) {
		if (StringUtil.isBlank(value)) {
			return null;
		}
		if (value.startsWith("N") || value.startsWith("n")) {
			value = value.substring(1);
			if (!NumberUtil.isInt(value)) {
				return null;
			}
			return -Integer.parseInt(value);
		} else {
			if (!NumberUtil.isInt(value)) {
				return null;
			}
			return Integer.parseInt(value);
		}
	}

	public Double parseRequestDouble(String name, HttpServletRequest request) {
		return parseRequestDouble(parseRequestString(name, request));
	}

	private Double parseRequestDouble(String value) {
		if (StringUtil.isBlank(value)) {
			return null;
		}
		if (!NumberUtil.isDouble(value)) {
			return null;
		}
		return Double.parseDouble(value);
	}

	public Long parseRequestLong(String name, HttpServletRequest request) {
		return parseRequestLong(parseRequestString(name, request));
	}

	private Long parseRequestLong(String value) {
		if (StringUtil.isBlank(value)) {
			return null;
		}
		if (!NumberUtil.isLong(value)) {
			return null;
		}
		return Long.parseLong(value);
	}

	public Boolean parseRequestBoolean(String name, HttpServletRequest request) {
		return parseRequestBoolean(parseRequestString(name, request));
	}

	private Boolean parseRequestBoolean(String value) {
		if (StringUtil.isBlank(value)) {
			return null;
		}
		value = value.toLowerCase().trim();
		if (value.equals("true") || value.equals("1")) {
			return Boolean.TRUE;
		} else if (value.equals("false") || value.equals("0")) {
			return Boolean.FALSE;
		}
		return null;
	}

	public List<UploadFile> getFiles(String uploadPath, int maxPostSize) {
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest == null) {
			throw new RuntimeException("form enctype is not multipart/form-data!");
		}
		multipartRequest.setMaxPostSize(maxPostSize);
		return multipartRequest.parseFiles(uploadPath);
	}

	public UploadFile getFile(String parameterName, String uploadPath, int maxPostSize) {
		getFiles(uploadPath, maxPostSize);
		return getFile(parameterName);
	}

	public List<UploadFile> getFiles(String uploadPath) {
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest == null) {
			throw new RuntimeException("form enctype is not multipart/form-data!");
		}
		return multipartRequest.parseFiles(uploadPath);
	}

	public UploadFile getFile(String parameterName, String uploadPath) {
		getFiles(uploadPath);
		return getFile(parameterName);
	}

	public List<UploadFile> getFiles() {
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest == null) {
			throw new RuntimeException("form enctype is not multipart/form-data!");
		}
		return multipartRequest.parseFiles();
	}

	public UploadFile getFile() {
		List<UploadFile> uploadFiles = getFiles();
		return uploadFiles.size() > 0 ? uploadFiles.get(0) : null;
	}

	public UploadFile getFile(String parameterName) {
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest == null) {
			throw new RuntimeException("form enctype is not multipart/form-data!");
		}
		return multipartRequest.parseFile(parameterName);
	}

	public <T> T parseRequestObject(String name, Type type, AtomarrowRequest request) {
		if (request.isJSONRequest()) {
			return parseRequestObjectByJSON(name, type, request);
		}
		if (type instanceof Class == false) { //非json请求，该方法只能处理普通类，不支持泛型类型，
			throw new TypeMismatchException("不支持转化为" + type + "类型");
		}

		return (T) parseRequestObjectByDefault(name, (Class) type, request);
	}

	/**
	 * 从json请求中转化对象
	 * @param name
	 * @param type
	 * @param request
	 * @param <T>
	 * @return
	 */
	public <T> T parseRequestObjectByJSON(String name, Type type, AtomarrowRequest request) {
		if (request.isJSONRequest()) {
			return request.getJsonData().getObject(name, type);
		}
		throw new TypeMismatchException("该请求非JSON请求，无法从json转换参数");
	}


	/**
	 * 从普通请求中转化对象
	 * @param name
	 * @param clazz
	 * @param request
	 * @param <T>
	 * @return
	 */
	public <T> T parseRequestObjectByDefault(String name, Class<T> clazz, HttpServletRequest request) {
		boolean exist = false;
		String objectNameAndDot = name + ".";
		String objectNameAndBracket = name + "[";
		Object obj = null;
		boolean isDomain = false;
		Method addUpdateFiledMethod = null;
		try {
			obj = clazz.newInstance();
			if (obj instanceof Domain) {
				isDomain = true;
				addUpdateFiledMethod = clazz.getMethod("addUpdateField", String.class);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		Map<String, String[]> parasMap = null;
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest != null) {
			parasMap = multipartRequest.getParameterMap();
		} else {
			parasMap = request.getParameterMap();
		}
		for (Entry<String, String[]> entry : parasMap.entrySet()) {
			String paraKey = entry.getKey();
			Field field = null;
			String paraName = null;
			if (paraKey.startsWith(objectNameAndDot)) {
				exist = true;
				try {
					paraName = paraKey;
					if (StringUtil.count(paraKey, '.') >= 2) {
						throw new Exception("\".\" the number of times up to 1 times");
					}
					paraName = paraKey.substring(objectNameAndDot.length());
					field = clazz.getDeclaredField(paraName);
					if (field == null) {
						throw new Exception("The object field " + paraKey + " is not exists.");
					}
					String[] paraValue = entry.getValue();
					field.setAccessible(true);
					if (needSetMethod(field)) {
						Method method = obj.getClass().getMethod("set" + StringUtil.toUpperCaseFirstWord(paraName),
								field.getType());
						if (method == null) {
							throw new Exception(
									"set" + StringUtil.toUpperCaseFirstWord(paraName) + " method not found");
						}
						method.invoke(obj, typeConverter.converter(paraValue, field.getType()));
					} else {
						field.set(obj, typeConverter.converter(paraValue, field.getType()));
					}

					if (isDomain) {
						addUpdateFiledMethod.invoke(obj, paraName);
					}
				} catch (Exception ex) {
					throw new RuntimeException("Can not convert parameter: " + objectNameAndDot + paraName, ex);
				}
			} else if (paraKey.startsWith(objectNameAndBracket)) {

				exist = true;
				try {
					paraName = paraKey;
					if (StringUtil.count(paraKey, '[') >= 3) {
						throw new Exception("\"[\" the number of times up to 2 times");
					}
					paraName = paraKey.substring(objectNameAndBracket.length()).replaceAll("]", "");
					field = clazz.getDeclaredField(paraName);
					String[] paraValue = entry.getValue();
					field.setAccessible(true);
					field.set(obj, typeConverter.converter(paraValue, field.getType()));
				} catch (Exception ex) {
					if (ex instanceof NoSuchFieldException){
						System.out.println("The object field " + paraKey + " is not exists.");
						continue;
					}
					throw new RuntimeException("Can not convert parameter: " + objectNameAndBracket + paraName + "]",
							ex);
				}

			}
		}
		if (exist == false) {
			return null;
		}
		return (T) obj;
	}

	/**
	 * 判断是否需要使用set方法来赋值
	 * 
	 * @param field
	 * @return
	 */
	private boolean needSetMethod(Field field) {
		return field.isAnnotationPresent(UseSetMethod.class);
	}


	/**
	 * 转化 List 的统一接口
	 * @param name
	 * @param listType JSON请求，会优先根据该type转化
	 * @param elementType listType 无法提供时，会再根据componentType转化，效率会低一些
	 * @param request
	 * @param <T>
	 * @return
	 */
	public <T> List<T> parseRequestList(String name, Type listType, Type elementType, AtomarrowRequest request) {

		if (request.isJSONRequest()) {
			return parseRequestListByJSON(name, listType, elementType, request);
		}

		if (elementType instanceof Class == false) {
			throw new TypeMismatchException("不支持转化为List<" + elementType + ">类型");
		}

		return (List<T>) parseRequestListByDefault(name, (Class)elementType, request);
	}

	/**
	 * JSON请求中转化List
	 * @param name
	 * @param listType
	 * @param elementType
	 * @param request
	 * @param <T>
	 * @return
	 */
	public <T> List<T> parseRequestListByJSON(String name, Type listType, Type elementType, AtomarrowRequest request) {

		if (listType != null) {
			return request.getJsonData().getObject(name, listType);
		}
		//这种效率会低，当JSON请求时，为了兼容调用 getParamList("key", TbUser.class) 提供这种方式
		return request.getJsonData().getObject(name, new TypeReference<List<T>>(elementType){});
	}

	/**
	 * 普通请求中转化List
	 * @param name
	 * @param clazz
	 * @param request
	 * @param <T>
	 * @return
	 */
	public <T> List<T> parseRequestListByDefault(String name, Class<T> clazz, HttpServletRequest request) {
		// 首先提取标号列表
		List<String> nos = getParamNumberList(name, request);
		List<T> list = new ArrayList<T>();
		for (String no : nos) {
			T m = parseRequestObject(name + "[" + no + "]", clazz, (AtomarrowRequest) request);
			if (m != null) {
				list.add(m);
			}
		}
		return list;
	}

	/**
	 * 得到数组下标列表 提取标号
	 * 
	 * @param name
	 * @param request
	 * @return
	 */
	protected List<String> getParamNumberList(String name, HttpServletRequest request) {
		Map<String, String[]> parasMap = null;
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest != null) {
			parasMap = multipartRequest.getParameterMap();
		} else {
			parasMap = request.getParameterMap();
		}
		List<String> list = new ArrayList<String>();
		String paramNameAndLeft = name + "[";

		for (Map.Entry<String, String[]> e : parasMap.entrySet()) {
			String paraKey = e.getKey();
			if (paraKey.startsWith(paramNameAndLeft)) {
				String no = paraKey.substring(paraKey.indexOf("[") + 1, paraKey.indexOf("]"));
				if (!list.contains(no)) {
					list.add(no);
				}
			}
		}
		Collections.sort(list);
		return list;
	}

	public String parseRequestString(String name, HttpServletRequest request) {
		MultipartRequest multipartRequest = ActionContext.getContext().getMultipartRequest();
		if (multipartRequest != null) {
			return multipartRequest.getParameter(name);
		}
		String result;
		if (request.getMethod().equals("GET")) {
			result = request.getParameter(name);
			if (result != null) {
				Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
				Matcher m = p.matcher(result);
				// 如果没有发现中文，说明使用get方式传递了中文，需要使用下面的方法转换
				if (!m.find()) {
					try {
						result = new String(result.getBytes("iso8859-1"), StandardCharsets.UTF_8);
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
				}
			}
		} else {
			result = request.getParameter(name);
		}
		if (result != null) {
			return result;
		}
		result = (String) request.getAttribute(name);
		return result;
	}

	public String parseRequestString(int index, HttpServletRequest request) {
		return ActionContext.getContext().getUrlParam(index);
	}

	public Date parseRequestDate(int index, HttpServletRequest request) {
		return parseDateByPattern(ActionContext.getContext().getUrlParam(index), null);
	}

	public Date parseRequestDate(int index, String pattern, HttpServletRequest request) {
		return parseDateByPattern(ActionContext.getContext().getUrlParam(index), pattern);
	}

	public Integer parseRequestInteger(int index, HttpServletRequest request) {
		return parseRequestInteger(ActionContext.getContext().getUrlParam(index));
	}

	public Double parseRequestDouble(int index, HttpServletRequest request) {
		return parseRequestDouble(ActionContext.getContext().getUrlParam(index));
	}

	public Long parseRequestLong(int index, HttpServletRequest request) {
		return parseRequestLong(ActionContext.getContext().getUrlParam(index));
	}

	public Boolean parseRequestBoolean(int index, HttpServletRequest request) {
		return parseRequestBoolean(ActionContext.getContext().getUrlParam(index));
	}

	public <T> T[] parseRequestArray(String name, Type arrayType, Type elementType, AtomarrowRequest request) {

		if (request.isJSONRequest()) {
			return parseRequestArrayByJSON(name, arrayType, elementType, request);
		}

		if (elementType instanceof Class == false) {
			throw new TypeMismatchException("不支持转化成" + elementType + "[]数组");
		}

		if (elementType == String.class) {
			return (T[]) parseRequestArrayByDefault(name,  request);
		}

		if (elementType == Integer.class || elementType == int.class) {
			return (T[])parseRequestIntArrayByDefault(name, request);
		}

		throw new TypeMismatchException("不支持转化成" + elementType + "[]数组");
	}

	public <T> T[] parseRequestArrayByJSON(String name, Type arrayType, Type elementType, AtomarrowRequest request) {
		if (arrayType != null) {
			return request.getJsonData().getObject(name, arrayType);
		}
		//为了兼容getParamArray ，但是 fastjson 没提供根据elementType 转化成对应数组的方法，所以只能变通下，先转成List，再转数组，
		//所以这种方式性能非常低下，在json请求下，尽量不要这么用
		List<T> list = parseRequestListByJSON(name, null, elementType, request);

		return list == null ? null :  list.toArray((T[]) Array.newInstance((Class<?>) elementType, list.size()));
	}


	public String[] parseRequestArrayByDefault(String name, HttpServletRequest request) {
		return request.getParameterValues(name);
	}

	public Integer[] parseRequestIntArrayByDefault(String name, HttpServletRequest request) {
		String[] values = parseRequestArrayByDefault(name, request);
		if (values == null) {
			return null;
		}
		Integer[] intValues = new Integer[values.length];
		int i = 0;
		for (String value : values) {
			if (NumberUtil.isInt(value)) {
				intValues[i] = NumberUtil.getInt(value);
			} else {
				throw new RuntimeException(value + "不是数字");
			}
			i++;
		}
		return intValues;
	}


	public <E> E parseRequestEnum(String name, Class<E> type, AtomarrowRequest request) {
		String value = parseRequestString(name, request);
		if (StringUtil.isBlank(value)) {
			return null;
		}

		return EnumUtil.getValueByName(type, value);

	}

	public <E> E parseRequestEnum(int index, Class<E> type, AtomarrowRequest request) {
		return EnumUtil.getValueByName(type, ActionContext.getContext().getUrlParam(index));
	}

}
